/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022.
 * Description: hkids kernel function path options.
 * Author: weiyuchen
 * Create: 2022-11-16
 */

#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/refcount.h>
#include <linux/task_struct_extend.h>
#include <linux/hkids/hkids_path.h>

static inline void ref_hkids_path(struct hkids_path_struct *hkids_path)
{
	refcount_inc(&hkids_path->ref);
}

static inline bool deref_hkids_path(struct hkids_path_struct *hkids_path)
{
	return refcount_dec_and_test(&hkids_path->ref);
}

void free_hkids_path(struct hkids_path_struct *hkids_path)
{
	if (!hkids_path)
		return;
	if (!deref_hkids_path(hkids_path))
		return;
	kfree(hkids_path->path);
	hkids_path->path = NULL;
	kfree(hkids_path);
}

static struct hkids_path_struct *dup_hkids_path(
	const struct hkids_path_struct *oldh)
{
	struct hkids_path_struct *new = NULL;

	new = kzalloc(sizeof(struct hkids_path_struct), GFP_KERNEL);
	if (unlikely(!new))
		return ERR_PTR(-ENOMEM);
	/* new->path could be NULL, but it will be judged when it used */
	new->path = kstrdup(oldh->path, GFP_KERNEL);
	refcount_set(&new->ref, 1);
	return new;
}

/*
 * copy_hkids_path: copy hkids_path_struct when a process do clone
 * syscall, it will reuse the old process' hkids_path->path
 * but when a process is cloned, hkids_path->ref need to reset to 1
 * when a thread is cloned, hkids_path->ref need to increase 1
 */
int copy_hkids_path(const unsigned long flags, struct task_struct *tsk)
{
	struct hkids_path_struct *newh;
	struct hkids_path_struct *oldh = task_to_rtos_task(current)->hkids_path;
	struct rtos_task_struct *rtos = task_to_rtos_task(tsk);

	if (!oldh)
		return -ESRCH;

	if (!rtos)
		return -ESRCH;

	if (flags & CLONE_THREAD) {
		ref_hkids_path(oldh);
		rtos->hkids_path = oldh;
		return 0;
	}

	newh = dup_hkids_path(oldh);
	if (IS_ERR(newh))
		return PTR_ERR(newh);

	rtos->hkids_path = newh;
	return 0;
}

/*
 * update_hkids_path: update hkids_path->path value when a process
 * do exec syscall, it will use new binary
 */
int update_hkids_path(const struct file *exefile, struct task_struct *tsk)
{
	struct rtos_task_struct *new = task_to_rtos_task(tsk);
	char *process_path;
	char *kbuf;

	if (!exefile || !tsk)
		return -ESRCH;

	kbuf = kzalloc(sizeof(char) * PATH_MAX, GFP_KERNEL);
	if (!kbuf)
		return -ENOMEM;
	process_path = d_path(&exefile->f_path, kbuf, PATH_MAX);
	if (IS_ERR(process_path)) {
		kfree(kbuf);
		return PTR_ERR(process_path);
	}
	kfree(new->hkids_path->path);
	new->hkids_path->path = kstrdup(process_path, GFP_KERNEL);
	if (!new->hkids_path->path) {
		kfree(kbuf);
		return -ENOMEM;
	}

	kfree(kbuf);
	return 0;
}

/*
 * get_hkids_path: get process realpath from hkids_path_struct
 * use kstrdup to alloc new space for process path
 */
char *get_hkids_path(struct task_struct *tsk)
{
	struct rtos_task_struct *rtos_task;
	struct hkids_path_struct *hkids_path;
	char *process_path;

	if (!tsk)
		return NULL;
	/* rtos_task always not NULL */
	rtos_task = task_to_rtos_task(tsk);
	hkids_path = rtos_task->hkids_path;
	if (!hkids_path)
		return NULL;
	process_path = kstrdup(hkids_path->path, GFP_ATOMIC);
	if (!process_path)
		process_path = kstrdup(tsk->comm, GFP_ATOMIC);
	return process_path;
}
EXPORT_SYMBOL_NS(get_hkids_path, HW_RTOS_NS);

/*
 * put_hkids_path: free process_path
 */
void put_hkids_path(const char *process_path)
{
	kfree(process_path);
}
EXPORT_SYMBOL_NS(put_hkids_path, HW_RTOS_NS);
